1 /**
2  * Singly-linked list.
3  * Copyright: © 2015 Economic Modeling Specialists, Intl.
4  * Authors: Brian Schott
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module containers.slist;
9 
10 private import containers.internal.node : shouldAddGCRange;
11 private import std.experimental.allocator.mallocator : Mallocator;
12 
13 /**
14  * Single-linked allocator-backed list.
15  * Params:
16  *     T = the element type
17  *     Allocator = the allocator to use. Defaults to `Mallocator`.
18  *     supportGC = true if the container should support holding references to
19  *         GC-allocated memory.
20  */
21 struct SList(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T)
22 {
23 	/// Disable copying.
24 	this(this) @disable;
25 
26 	private import std.experimental.allocator.common : stateSize;
27 
28 	static if (stateSize!Allocator != 0)
29 	{
30 		/// No default construction if an allocator must be provided.
31 		this() @disable;
32 
33 		/**
34 		 * Use the given `allocator` for allocations.
35 		 */
36 		this(Allocator allocator)
37 		in
38 		{
39 			assert(allocator !is null, "Allocator must not be null");
40 		}
41 		do
42 		{
43 			this.allocator = allocator;
44 		}
45 	}
46 
47 	~this()
48 	{
49 		Node* current = _front;
50 		Node* prev = null;
51 		while (current !is null)
52 		{
53 			prev = current;
54 			current = current.next;
55 			typeid(Node).destroy(prev);
56 			static if (useGC)
57 			{
58 				import core.memory : GC;
59 				GC.removeRange(prev);
60 			}
61 			allocator.dispose(prev);
62 		}
63 		_front = null;
64 	}
65 
66 	/**
67 	 * Complexity: O(1)
68 	 * Returns: the most recently inserted item
69 	 */
70 	auto front(this This)() inout @property
71 	in
72 	{
73 		assert (!empty, "Accessing .front of empty SList");
74 	}
75 	do
76 	{
77 		alias ET = ContainerElementType!(This, T);
78 		return cast(ET) _front.value;
79 	}
80 
81 	/**
82 	 * Complexity: O(length)
83 	 * Returns: the least recently inserted item.
84 	 */
85 	auto back(this This)() inout @property
86 	in
87 	{
88 		assert (!empty, "Accessing .back of empty SList");
89 	}
90 	do
91 	{
92 		alias ET = ContainerElementType!(This, T);
93 
94 		auto n = _front;
95 		for (; front.next !is null; n = n.next) {}
96 		return cast(ET) n.value;
97 	}
98 
99 	/**
100 	 * Removes the first item in the list.
101 	 *
102 	 * Complexity: O(1)
103 	 * Returns: the first item in the list.
104 	 */
105 	T moveFront()
106 	in
107 	{
108 		assert (!empty, "Accessing .moveFront of empty SList");
109 	}
110 	do
111 	{
112 		Node* f = _front;
113 		_front = f.next;
114 		T r = f.value;
115 		static if (useGC)
116 		{
117 			import core.memory : GC;
118 			GC.removeRange(f);
119 		}
120 		allocator.dispose(f);
121 		--_length;
122 		return r;
123 	}
124 
125 	/**
126 	 * Removes the first item in the list.
127 	 *
128 	 * Complexity: O(1)
129 	 */
130 	void popFront()
131 	{
132 		Node* f = _front;
133 		_front = f.next;
134 		static if (useGC)
135 		{
136 			import core.memory : GC;
137 			GC.removeRange(f);
138 		}
139 		allocator.dispose(f);
140 		--_length;
141 	}
142 
143 	/**
144 	 * Complexity: O(1)
145 	 * Returns: true if this list is empty
146 	 */
147 	bool empty() inout pure nothrow @property @safe @nogc
148 	{
149 		return _front is null;
150 	}
151 
152 	/**
153 	 * Complexity: O(1)
154 	 * Returns: the number of items in the list
155 	 */
156 	size_t length() inout pure nothrow @property @safe @nogc
157 	{
158 		return _length;
159 	}
160 
161 	/**
162 	 * Inserts an item at the front of the list.
163 	 *
164 	 * Complexity: O(1)
165 	 * Params: t = the item to insert into the list
166 	 */
167 	void insertFront(T t) @trusted
168 	{
169 		_front = make!Node(allocator, _front, t);
170 		static if (useGC)
171 		{
172 			import core.memory : GC;
173 			GC.addRange(_front, Node.sizeof);
174 		}
175 		_length++;
176 	}
177 
178 	/// ditto
179 	alias insert = insertFront;
180 
181 	/// ditto
182 	alias insertAnywhere = insertFront;
183 
184 	/// ditto
185 	alias put = insertFront;
186 
187 	/**
188 	 * Supports `list ~= item` syntax
189 	 *
190 	 * Complexity: O(1)
191 	 */
192 	void opOpAssign(string op)(T t) if (op == "~")
193 	{
194 		put(t);
195 	}
196 
197 	/**
198 	 * Removes the first instance of value found in the list.
199 	 *
200 	 * Complexity: O(length)
201 	 * Returns: true if a value was removed.
202 	 */
203 	bool remove(V)(V value) @trusted
204 	{
205 		Node* prev = null;
206 		Node* cur = _front;
207 		while (cur !is null)
208 		{
209 			if (cur.value == value)
210 			{
211 				if (prev !is null)
212 					prev.next = cur.next;
213 				if (_front is cur)
214 					_front = cur.next;
215 				static if (shouldAddGCRange!T)
216 				{
217 					import core.memory : GC;
218 					GC.removeRange(cur);
219 				}
220 				allocator.dispose(cur);
221 				_length--;
222 				return true;
223 			}
224 			prev = cur;
225 			cur = cur.next;
226 		}
227 		return false;
228 	}
229 
230 	/**
231 	 * Forward range interface
232 	 */
233 	auto opSlice(this This)() inout
234 	{
235 		return Range!(This)(_front);
236 	}
237 
238 	/**
239 	 * Removes all elements from the range
240 	 *
241 	 * Complexity: O(length)
242 	 */
243 	void clear()
244 	{
245 		Node* prev = null;
246 		Node* cur = _front;
247 		while (cur !is null)
248 		{
249 			prev = cur;
250 			cur = prev.next;
251 			static if (shouldAddGCRange!T)
252 			{
253 				import core.memory : GC;
254 				GC.removeRange(prev);
255 			}
256 			allocator.dispose(prev);
257 		}
258 		_front = null;
259 		_length = 0;
260 	}
261 
262 private:
263 
264 	import std.experimental.allocator : make, dispose;
265 	import containers.internal.node : shouldAddGCRange;
266 	import containers.internal.element_type : ContainerElementType;
267 	import containers.internal.mixins : AllocatorState;
268 
269 	enum bool useGC = supportGC && shouldAddGCRange!T;
270 
271 	static struct Range(ThisT)
272 	{
273 	public:
274 		ET front() pure nothrow @property @trusted @nogc
275 		{
276 			return cast(typeof(return)) current.value;
277 		}
278 
279 		void popFront() pure nothrow @safe @nogc
280 		{
281 			current = current.next;
282 		}
283 
284 		bool empty() const pure nothrow @property @safe @nogc
285 		{
286 			return current is null;
287 		}
288 
289 	private:
290 		alias ET = ContainerElementType!(ThisT, T);
291 		const(Node)* current;
292 	}
293 
294 	static struct Node
295 	{
296 		Node* next;
297 		T value;
298 	}
299 
300 	mixin AllocatorState!Allocator;
301 	Node* _front;
302 	size_t _length;
303 }
304 
305 version(emsi_containers_unittest) unittest
306 {
307 	import std..string : format;
308 	import std.algorithm : canFind;
309 	SList!int intList;
310 	foreach (i; 0 .. 100)
311 		intList.put(i);
312 	assert(intList.length == 100, "%d".format(intList.length));
313 	assert(intList.remove(10));
314 	assert(!intList.remove(10));
315 	assert(intList.length == 99);
316 	assert(intList[].canFind(9));
317 	assert(!intList[].canFind(10));
318 	SList!string l;
319 	l ~= "abcde";
320 	l ~= "fghij";
321 	assert (l.length == 2);
322 }
323 
324 version(emsi_containers_unittest) unittest
325 {
326 	static class Foo
327 	{
328 		string name;
329 	}
330 
331 	SList!Foo hs;
332 	auto f = new Foo;
333 	hs.put(f);
334 }